package com.lagou.edu.mvcaframework.servlet;

import com.lagou.edu.mvcaframework.annotations.LagouAutowired;
import com.lagou.edu.mvcaframework.annotations.LagouController;
import com.lagou.edu.mvcaframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcaframework.annotations.LagouSecurity;
import com.lagou.edu.mvcaframework.annotations.LagouService;
import com.lagou.edu.projo.Handler;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LagouDispatcherServlet extends HttpServlet {

  private Properties properties = new Properties();

  private List<String> beanNames = new ArrayList<String>(); // beans权限的类名的集合

  // ioc容器
  private Map<String, Object> ioc = new HashMap<String, Object>();

  private List<Handler> handleMapping = new ArrayList<Handler>();

  @Override
  public void init(ServletConfig config) throws ServletException {

    String contextConfigLocation =
        config.getInitParameter("contextConfigLocation").replace("classpath*:", "");

    // 1 加载配置文件 springmvc.properties
    doLoadConfig(contextConfigLocation);

    // 2 扫描相关的类，扫描注解
    doScan(properties.getProperty("scanPackage"));

    // 3 初始化bean对象（实现ioc容器，基于注解）
    doInstance();

    // 4 实现依赖注入
    doAutoWired();

    // 5 构造一个HandlerMapping处理器映射器，将配置好的url和Method建立映射关系
    initHandlerMapping();

    System.out.println("lagout mvc 初始化完成......");
  }

  /** 构造一个HandlerMapping处理器映射器，将配置好的url和Method建立映射关系 */
  private void initHandlerMapping() {
    Set<Map.Entry<String, Object>> entries = ioc.entrySet();
    if (entries.isEmpty()) {
      return;
    }

    // 循环遍历
    for (Map.Entry<String, Object> entry : entries) {
      Class<?> aClass = entry.getValue().getClass();
      if (!aClass.isAnnotationPresent(LagouController.class)
          || !aClass.isAnnotationPresent(LagouRequestMapping.class)) {
        // 没有@LagouController注解和@LagouRequestMapping的类跳过,
        continue;
      }
      LagouRequestMapping requestMappingAnnotation =
          entry.getValue().getClass().getAnnotation(LagouRequestMapping.class);
      if (requestMappingAnnotation == null) {
        // 没有@LagouRequestMapping注解，跳过
        continue;
      }

      String bathUrl = requestMappingAnnotation.value();

      Method[] methods = aClass.getDeclaredMethods();
      if (methods == null || methods.length == 0) {
        continue;
      }

      // 循环遍历方法
      for (Method method : methods) {
        // 获取方法上的@LagouRequestMapping注解
        LagouRequestMapping methodAnnotation = method.getAnnotation(LagouRequestMapping.class);
        if (methodAnnotation == null) {
          continue;
        }

        String url = methodAnnotation.value();
        if (StringUtils.isBlank(url)) {
          continue;
        }

        String realUrl = bathUrl + url;

        // 构建Handlerd对象
        Handler handler = new Handler(entry.getValue(), method, Pattern.compile(realUrl));

        // 解析参数列表
        Parameter[] parameters = method.getParameters();

        Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
        for (int i = 0; i < parameters.length; i++) {
          // 判断参数是否为HttpServletRequet,如果有
          String name = parameters[i].getType().getSimpleName();

          if ("HttpServletRequest".equals(name)) {
            paramIndexMapping.put("HttpServletRequest", 0);
          } else if ("HttpServletResponse".equals(name)) {
            paramIndexMapping.put("HttpServletResponse", 1);
          } else {
            paramIndexMapping.put(parameters[i].getName(), i);
          }
          handler.setParamIndexMapping(paramIndexMapping);
        }
        handleMapping.add(handler);
      }
    }
  }

  /** 实现依赖注入 */
  private void doAutoWired() {
    if (ioc.isEmpty()) return;

    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
      String key = entry.getKey(); // 获取键(beanId)
      Object value = entry.getValue(); // 获取值(对象)

      // 获取类中的所有属性的值
      Field[] fields = value.getClass().getDeclaredFields();
      // 判断是为空，如果为空不处理
      if (fields != null && fields.length > 0) {
        for (Field declaredField : fields) {
          // 判断属性上是否有@LagouService注解
          if (declaredField.isAnnotationPresent(LagouAutowired.class)) {
            LagouAutowired autowiredAnnotation = declaredField.getAnnotation(LagouAutowired.class);

            String beanName = "";
            // 判断value是否有值
            if (StringUtils.isNotBlank(autowiredAnnotation.value())) {
              // value有值
              beanName = autowiredAnnotation.value();
            } else {
              // value没有值，取类名首字母小写
              beanName = fristLower(declaredField.getType().getSimpleName());
            }

            Object bean = ioc.get(beanName);
            declaredField.setAccessible(true);

            // 通过反射，进行属性注入
            try {
              declaredField.set(value, bean);
            } catch (IllegalAccessException e) {
              e.printStackTrace();
            }
          }
        }
      }
    }
  }

  /** 初始化bean对象（实现ioc容器，基于注解） */
  private void doInstance() {
    // 循环beanName集合
    for (String beanName : beanNames) {
      // 通过反射获取类
      try {
        Class<?> aClass = Class.forName(beanName);
        // 判断类上是否有@LagouController、@LagouService注解
        if (aClass.isAnnotationPresent(LagouController.class)) {
          // controller直接放到map中
          ioc.put(fristLower(aClass.getSimpleName()), aClass.newInstance());
        } else if (aClass.isAnnotationPresent(LagouService.class)) {
          // server要处理一下，判断是否有value属性
          // 获取类名首字母小写，并且获取接口的首字母小写
          LagouService annotation = aClass.getAnnotation(LagouService.class);
          String annotationValue = annotation.value();
          if (StringUtils.isNotBlank(annotationValue)) {
            // 如果有直接用value，如果没有
            ioc.put(annotationValue, aClass.newInstance());
          } else {
            ioc.put(fristLower(aClass.getSimpleName()), aClass.newInstance());
            // 获取类实现的接口
            Class<?>[] interfaces = aClass.getInterfaces();
            if (interfaces != null && interfaces.length > 0) {
              for (Class<?> anInterface : interfaces) {
                ioc.put(fristLower(anInterface.getSimpleName()), aClass.newInstance());
              }
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 类名首字符转小写
   *
   * @param beanName
   * @return
   */
  private String fristLower(String beanName) {
    String result =
        beanName.substring(0, 1).toLowerCase() + beanName.substring(1, beanName.length());
    return result;
  }

  /**
   * 扫描相关的类，扫描注解
   *
   * @param scanPackage
   */
  private void doScan(String scanPackage) {
    // 找到对应的C盘路径 com.lagou.edu.demo
    String path =
        Thread.currentThread().getContextClassLoader().getResource("").getPath()
            + scanPackage.replaceAll("\\.", "\\/");

    File file = new File(path);

    // 获取当前路径下的所有文件
    File[] files = file.listFiles();
    for (File tempFile : files) {
      // 判断当前记录是否为文件夹
      if (tempFile.isDirectory()) {
        // 是文件夹，迭代
        doScan(scanPackage + "." + tempFile.getName());
      } else if (tempFile.getName().endsWith(".class")) {
        String className = scanPackage + "." + tempFile.getName().replaceAll(".class", "");
        beanNames.add(className);
      }
    }
  }

  /**
   * 加载springmvc.properties配置文件
   *
   * @param contextConfigLocation
   */
  private void doLoadConfig(String contextConfigLocation) {
    InputStream resourceAsStream =
        this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
    try {
      properties.load(resourceAsStream);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    doPost(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    Handler handler = getHandler(req);

    if (handler == null) {
      resp.getWriter().write("404 not found");
      return;
    }

    // 判断当前controller是否有LagouSecurity标签
    Object controller = handler.getController();
    if (controller.getClass().isAnnotationPresent(LagouSecurity.class)) {
      // 当前controller有@LagouSecurity标签时，获取标签的值
      LagouSecurity annotation = controller.getClass().getAnnotation(LagouSecurity.class);
      String[] userNameArray = annotation.value();
      if (userNameArray != null && userNameArray.length > 0) {
        // 从参数中获取username属性
        String username = req.getParameter("username");
        if (StringUtils.isEmpty(username) || !Arrays.asList(userNameArray).contains(username)) {
          resp.getWriter().write("403 No authority");
          return;
        }
      }
    }

    // 判断当前handel方法上是否有LagouSecurity标签
    Method method = handler.getMethod();
    if (method.isAnnotationPresent(LagouSecurity.class)) {
      // 当前方法有@LagouSecurity标签时，获取标签的值
      LagouSecurity annotation = method.getAnnotation(LagouSecurity.class);
      String[] userNameArray = annotation.value();
      if (userNameArray != null && userNameArray.length > 0) {
        // 从参数中获取username属性
        String username = req.getParameter("username");
        if (StringUtils.isEmpty(username) || !Arrays.asList(userNameArray).contains(username)) {
          resp.getWriter().write("403 No authority");
          return;
        }
      }
    }

    Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

    // 根据上述数组长度创建一个新的数组（参数数组，是要传入反射调用的）
    Object[] paraValues = new Object[parameterTypes.length];

    Map<String, String[]> parameterMap = req.getParameterMap();

    // 遍历request中所有参数  （填充除了request，response之外的参数）
    for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
      // name=1&name=2   name [1,2]
      String value = StringUtils.join(param.getValue(), ","); // 如同 1,2

      // 如果参数和方法中的参数匹配上了，填充数据
      if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
        continue;
      }

      // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paraValues
      Integer index = handler.getParamIndexMapping().get(param.getKey()); // name在第 2 个位置

      paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去
    }

    int requestIndex =
        handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
    paraValues[requestIndex] = req;

    int responseIndex =
        handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
    paraValues[responseIndex] = resp;

    try {
      handler.getMethod().invoke(handler.getController(), paraValues);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
  }

  // 获取handler
  private Handler getHandler(HttpServletRequest req) {
    // 从请求中获取url
    String requestURI = req.getRequestURI();
    if (handleMapping.isEmpty()) {
      return null;
    }

    for (Handler handler : handleMapping) {
      if (!handler.getPattern().matcher(requestURI).matches()) {
        continue;
      }
      return handler;
    }
    return null;
  }
}
